/*
 * @Author: soso
 * @Date: 2022-03-15 11:22:37
 * @LastEditTime: 2022-03-15 13:35:35
 * @LastEditors: Please set LastEditors
 * @Description: 网络相关
 * @FilePath: /go-utils/utils/net.go
 */
package utils

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"os/exec"
	"strings"
	"sync"
)

var (
	ipOnce sync.Once
	hostIP string
)

var (
	localNetworks       = []net.IPNet{}
	localNetworkStrings = []string{
		"10.0.0.0/8",
		"169.254.0.0/16",
		"172.16.0.0/12",
		"172.17.0.0/12",
		"172.18.0.0/12",
		"172.19.0.0/12",
		"172.20.0.0/12",
		"172.21.0.0/12",
		"172.22.0.0/12",
		"172.23.0.0/12",
		"172.24.0.0/12",
		"172.25.0.0/12",
		"172.26.0.0/12",
		"172.27.0.0/12",
		"172.28.0.0/12",
		"172.29.0.0/12",
		"172.30.0.0/12",
		"172.31.0.0/12",
		"192.168.0.0/16",
	}
)

func init() {
	for _, ips := range localNetworkStrings {
		if _, ipnet, err := net.ParseCIDR(ips); err == nil {
			localNetworks = append(localNetworks, *ipnet)
		}
	}
}

/**
 * @description: 以两种方式获得本地IP
 * @param {*}
 * @return {*}
 */
func GetLocalIPOnce() string {
	ipOnce.Do(func() {
		_ip, err := GetOutBoundIP()
		if err != nil {
			_ip, err = GetClientIP()
			if err != nil {
				log.Println("Got local ip error: ", err)
			}
		}
		hostIP = _ip
	})
	return hostIP
}

/**
 * @description: 以两种方式获得本地IP
 * @param {*}
 * @return {*}
 */
func GetLocalIP() string {
	ip, err := GetOutBoundIP()
	if err != nil {
		ip, err = GetClientIP()
		if err != nil {
			log.Println("Got local ip error: ", err)
		}
	}
	return ip
}

// 通过UDP方式获取本地ip
func GetOutBoundIP() (ip string, err error) {
	conn, err := net.Dial("udp", "223.5.5.5:53")
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)
	ip = strings.Split(localAddr.String(), ":")[0]
	return
}

// 通过本地dns的方式得到IP
func GetIpByLocalDns(dnsIp string) (ip string, err error) {
	conn, err := net.Dial("udp", dnsIp+":53")
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)
	ip = strings.Split(localAddr.String(), ":")[0]
	return
}

// 通过提供子网段的方式获得ip
func GetIpBySubnet(subnet string) (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		fmt.Println(err)
		return
	}

	for _, address := range addrs {
		// 检查ip地址判断是否回环地址
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil && strings.HasPrefix(ipnet.IP.String(), subnet) {
				ip = ipnet.IP.String()
			}
		}
	}
	if ip == "" {
		err = errors.New("can't find this subnet")
	}
	return
}

// 通过本地InterfaceAddrs得到IP，绑定多IP情况下会出问题
func GetClientIP() (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		fmt.Println(err)
		return
	}
	ips := []string{}
	for _, address := range addrs {
		// 检查ip地址判断是否回环地址
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				ip = ipnet.IP.String()
				ips = append(ips, ip)
			}
		}
	}
	if len(ips) > 1 {
		err = errors.New("muliple ip got")
	}
	return
}

func OnInternet() bool {
	cmd := exec.Command("ping", "baidu.com", "-c", "1", "-W", "5")
	err := cmd.Run()
	return err == nil
}

func ClientIP(r *http.Request) string {
	xForwardedFor := r.Header.Get("X-Forwarded-For")
	ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
	if ip != "" {
		return ip
	}

	ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
	if ip != "" {
		return ip
	}

	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		return ip
	}

	return ""
}

// ClientPublicIP 尽最大努力实现获取客户端公网 IP 的算法。
// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理（nginx 或 haproxy）可以正常工作。
func ClientPublicIP(r *http.Request) string {
	var ip string
	for _, ip = range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
		ip = strings.TrimSpace(ip)
		if ip != "" && !HasLocalIPddr(ip) {
			return ip
		}
	}

	ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
	if ip != "" && !HasLocalIPddr(ip) {
		return ip
	}

	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		if !HasLocalIPddr(ip) {
			return ip
		}
	}

	return ""
}

// HasLocalIPddr 检测 IP 地址字符串是否是内网地址
func HasLocalIPddr(ip string) bool {
	return HasLocalIP(net.ParseIP(ip))
}

// HasLocalIP 检测 IP 地址是否是内网地址
func HasLocalIP(ip net.IP) bool {
	localNetworks := []net.IPNet{}
	for _, network := range localNetworks {
		if network.Contains(ip) {
			return true
		}
	}

	return ip.IsLoopback()
}
